﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace load;

[Serializable]
public class AssemblyDependent : IDisposable
{
#if NET48
	string _dllFile;
    /// <summary>
    /// cad程序域依赖_内存区(不可以卸载)
    /// </summary>
    private Assembly[] _cadAs;

    /// <summary>
    /// cad程序域依赖_映射区(不可以卸载)
    /// </summary>
    private Assembly[] _cadAsRef;

    /// <summary>
    /// 加载DLL成功后获取到的程序集
    /// </summary>
    public List<Assembly> MyLoadAssemblys { get; private set; }

    /// <summary>
    /// 当前域加载事件,运行时出错的话,就靠这个事件来解决
    /// </summary>
    public event ResolveEventHandler CurrentDomainAssemblyResolveEvent
    {
        add
        {
            AppDomain.CurrentDomain.AssemblyResolve += value;
        }
        remove
        {
            AppDomain.CurrentDomain.AssemblyResolve -= value;
        }
    }

    /// <summary>
    /// 链式加载dll依赖
    /// </summary>
    /// <param name="dllFile"></param>
    public AssemblyDependent(string dllFile)
    {
        _dllFile = Path.GetFullPath(dllFile);//相对路径要先转换 Path.GetFullPath(dllFile);

        //cad程序集的依赖
        _cadAs = AppDomain.CurrentDomain.GetAssemblies();

        //映射区
        _cadAsRef = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();

        //被加载的都存放在这里
        MyLoadAssemblys = new List<Assembly>();
    }

    /// <summary>
    /// 返回的类型,描述加载的错误
    /// </summary>
    public class LoadDllMessage
    {
        public string Path;
        public bool LoadYes;

        public LoadDllMessage(string path, bool loadYes)
        {
            Path = path;
            LoadYes = loadYes;
        }

        public override string ToString()
        {
            if (LoadYes)
            {
                return "加载成功:" + Path;
            }
            return "加载失败:" + Path;
        }
    }
    /// <summary>
    /// 加载信息集合
    /// </summary>
    List<LoadDllMessage> LoadYesList;


    bool _byteLoad;
    /// <summary>
    /// 加载程序集
    /// </summary>
    /// <param name="byteLoad">true字节加载,false文件加载</param>
    /// <returns>返回加载链的</returns>
    public List<LoadDllMessage> Load(bool byteLoad = true)
    {
        _byteLoad = byteLoad;
        if (!File.Exists(_dllFile))
        {
            throw new ArgumentNullException("路径不存在");
        }
        LoadYesList = new List<LoadDllMessage>();

        //查询加载链之后再逆向加载,确保前面不丢失
        var allRefs = GetAllRefPaths(_dllFile);
        allRefs.Reverse();

        foreach (var path in allRefs)
        {
            try
            {
                //路径转程序集名
                string assName = AssemblyName.GetAssemblyName(path).FullName;
                //路径转程序集名
                var assembly = _cadAs.FirstOrDefault(a => a.FullName == assName);
                if (assembly != null)
                {
                    LoadYesList.Add(new LoadDllMessage(path, false));//版本号没变不加载
                    continue;
                }

                byte[] buffer = null;
                bool flag = true;
                //实现字节加载
                if (path == _dllFile)
                {
                    LoadOK = true;
                }
#if DEBUG
                //为了实现Debug时候出现断点,见链接,加依赖
                // https://www.cnblogs.com/DasonKwok/p/10510218.html
                // https://www.cnblogs.com/DasonKwok/p/10523279.html

                var dir = Path.GetDirectoryName(path);
                var pdbName = Path.GetFileNameWithoutExtension(path) + ".pdb";
                var pdbFullName = Path.Combine(dir, pdbName);
                if (File.Exists(pdbFullName) && _byteLoad)
                {
                    var pdbbuffer = File.ReadAllBytes(pdbFullName);
                    buffer = File.ReadAllBytes(path);
                    var ass = Assembly.Load(buffer, pdbbuffer);
                    MyLoadAssemblys.Add(ass);
                    flag = false;
                }
#endif
                if (flag)
                {
                    Assembly ass = null;
                    if (_byteLoad)
                    {
                        buffer = File.ReadAllBytes(path);
                        ass = Assembly.Load(buffer);
                    }
                    else
                    {
                        ass = Assembly.LoadFile(path);
                    }
                    MyLoadAssemblys.Add(ass);
                }
                LoadYesList.Add(new LoadDllMessage(path, true));//加载成功
            }
            catch
            {
                LoadYesList.Add(new LoadDllMessage(path, false));//错误造成
            }
        }
        MyLoadAssemblys.Reverse();
        return LoadYesList;
    }

    //链条后面的不再理会,因为相同的dll引用辨识无意义
    /// <summary>
    /// 第一个dll加载是否成功
    /// </summary>
    public bool LoadOK { get; private set; }

    /// <summary>
    /// 加载出错信息
    /// </summary>
    public string LoadErrorMessage
    {
        get
        {
            var sb = new StringBuilder();
            bool allyes = true;
            foreach (var item in LoadYesList)
            {
                if (!item.LoadYes)
                {
                    sb.Append(Environment.NewLine + "** 此文件已加载过,重复名称,重复版本号,本次不加载!");
                    sb.Append(Environment.NewLine + item.ToString());
                    sb.Append(Environment.NewLine);
                    allyes = false;
                }
            }
            if (allyes)
            {
                sb.Append(Environment.NewLine + "** 链式加载成功!");
                sb.Append(Environment.NewLine);
            }
            return sb.ToString();
        }
    }

    /// <summary>
    /// 获取加载链
    /// </summary>
    /// <param name="dll"></param>
    /// <param name="dlls"></param>
    /// <returns></returns>
    List<string> GetAllRefPaths(string dll, List<string> dlls = null)
    {
        if (dlls == null)
        {
            dlls = new List<string>();
        }
        if (dlls.Contains(dll) || !File.Exists(dll))
        {
            return dlls;
        }
        dlls.Add(dll);

        //路径转程序集名
        string assName = AssemblyName.GetAssemblyName(dll).FullName;

        //在当前程序域的assemblyAs内存区和assemblyAsRef映射区找这个程序集名
        Assembly assemblyAs = _cadAs.FirstOrDefault(a => a.FullName == assName);
        Assembly assemblyAsRef;

        //内存区有表示加载过
        //映射区有表示查找过但没有加载(一般来说不存在.只是debug会注释掉Assembly.Load的时候用来测试)
        if (assemblyAs != null)
        {
            assemblyAsRef = assemblyAs;
        }
        else
        {
            assemblyAsRef = _cadAsRef.FirstOrDefault(a => a.FullName == assName);

            //内存区和映射区都没有的话就把dll加载到映射区,用来找依赖表
            if (assemblyAsRef == null)
            {
                // assemblyAsRef = Assembly.ReflectionOnlyLoad(dll); 没有依赖会直接报错
                var byteRef = File.ReadAllBytes(dll);
                assemblyAsRef = Assembly.ReflectionOnlyLoad(byteRef);
            }
        }

        //遍历依赖,如果存在dll拖拉加载目录就加入dlls集合
        foreach (var assemblyName in assemblyAsRef.GetReferencedAssemblies())
        {
            //dll拖拉加载路径-搜索路径(可以增加到这个dll下面的所有文件夹?)
            string directoryName = Path.GetDirectoryName(dll);

            var path = directoryName + "\\" + assemblyName.Name;
            var paths = new string[]
            {
                path + ".dll",
                path + ".exe"
            };
            foreach (var patha in paths)
            {
                GetAllRefPaths(patha, dlls);
            }
        }
        return dlls;
    }



    /// <summary>
    /// 递归删除文件夹目录及文件
    /// </summary>
    /// <param name="dir"></param>
    /// <returns></returns>
    static void DeleteFolder(string dir)
    {
        if (Directory.Exists(dir)) //如果存在这个文件夹删除之
        {
            foreach (string d in Directory.GetFileSystemEntries(dir))
            {
                if (File.Exists(d))
                    File.Delete(d); //直接删除其中的文件
                else
                    DeleteFolder(d); //递归删除子文件夹
            }
            Directory.Delete(dir, true); //删除已空文件夹
        }
    }

    /// <summary>
    /// Debug的时候删除obj目录,防止占用
    /// </summary>
    public void DebugDelObjFiles()
    {
        try
        {
            var filename = Path.GetFileNameWithoutExtension(_dllFile);
            var path = Path.GetDirectoryName(_dllFile);

            var pdb = path + "\\" + filename + ".pdb";
            if (File.Exists(pdb))
            {
                File.Delete(pdb);
            }

            var list = path.Split('\\');
            if (list[list.Length - 1] == "Debug" && list[list.Length - 2] == "bin")
            {
                var bin = path.LastIndexOf("bin");
                var proj = path.Substring(0, bin);
                var obj = proj + "obj";
                DeleteFolder(obj);
            }
        }
        catch
        { 

        }
    }


#endif
	#region Dispose
	public bool Disposed = false;

    /// <summary>
    /// 显式调用Dispose方法,继承IDisposable
    /// </summary>
    public void Dispose()
    {
        //由手动释放
        Dispose(true);
        //通知垃圾回收机制不再调用终结器(析构器)_跑了这里就不会跑析构函数了
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 析构函数,以备忘记了显式调用Dispose方法
    /// </summary>
    ~AssemblyDependent()
    {
        //由系统释放
        Dispose(false);
    }


    /// <summary>
    /// 释放
    /// </summary>
    /// <param name="ing"></param>
    protected virtual void Dispose(bool ing)
    {
        if (Disposed)
        {
            //不重复释放
            return;
        }
        //让类型知道自己已经被释放
        Disposed = true;

        GC.Collect();
    }
#endregion

}